/**
 * @file ThreadPool.h
 * @author Michael Fetisov (fetisov.michael@bmstu.ru)
 * @brief 
 * @version 0.1
 * @date 2022-08-26
 * 
 * @copyright Copyright (c) 2022
 * 
 * Модифицированная версия пула потоков из книги 
 * Anthony Williams. C++ Concurrency in Action. Second Edition
 * 
 * Оптимизация работы (предотвращение нагрузки процессора) пока очередь пуста
 * с использованием std::condition_variable.
 * 
 */

#ifndef threadsafe_queue_h
#define threadsafe_queue_h

#include "simodo/tp/ThreadsafeQueue.h"
#include "simodo/tp/Task_interface.h"

#include <vector>
#include <functional>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>

namespace simodo::tp
{

/**
 * @brief Простейший пул потоков
 * 
 * @details Пул потоков выделяет заданное количество потоков, каждый из которых параллельно
 * выполняет задачи, которые добавляются в очередь посредством метода submit.
 * 
 * Задачи должны передаваться в виде указателя на объект, имеющий интерфейс tp::Task_interface.
 * 
 * Пул потоков завершает работу в случае выхода из зоны видимости и если очередь не пуста. Т.о. он
 * будет работать, пока не закончатся задачи в очереди.
 * 
 * @attention Объекты, которые передаются в пул потоков будут уничтожены автоматически после 
 * завершения их выполнения. БУДЬТЕ ОСТОРОЖНЫ! Не уничтожайте их самостоятельно!
 * 
 */
class ThreadPool
{
    int                      _number_of_threads;
    std::atomic_bool         _necessary_to_stop;
    std::condition_variable  _waiting_condition;
    std::mutex               _waiting_mutex;
    std::vector<std::thread> _threads;

    ThreadsafeQueue<Task_interface *> _task_queue;

    void worker();

public:
    /**
     * @brief Конструктор.
     * 
     * @param number_of_threads Количество потоков в пуле, которое необходимо задействовать. 
     * 
     * @details
     * Пустой конструктор создаст пул из количества потоков, которое будет равно логическим 
     * процессорам в компьютере (точнее, то, которое разрешено использовать операционной системе).
     * 
     * Однако можно указать количество потоков пуле. 
     * 
     * Если указать 0 потоков, то задания будут выполняться в главном потоке, 
     * т.е. в вызове submit, без задействования очереди и потоков.
     * 
     * Если указать отрицательное значение количества потоков, то будет выделено количество потоков 
     * равное количеству логических процессоров компьютера.
     * 
     */
    ThreadPool(int number_of_threads=-1, bool start_immediately=true);

    ~ThreadPool();

    /**
     * @brief Передача задания на исполнение пулу потоков.
     * 
     * @param task Указатель на объект-задание.
     * 
     * @details Если пул потоков работает в многопоточном режиме, то объект-задание будет 
     * размещён в очереди и в порядке очереди выполнен одним из потоков. Если пул потоков работает 
     * в беспотоковом режиме, то задание будет выполнено сразу, непосредственно в методе submit.
     * 
     * @attention Объекты, которые передаются в пул потоков будут уничтожены автоматически после 
     * завершения их выполнения. БУДЬТЕ ОСТОРОЖНЫ! Не уничтожайте их самостоятельно!
     * 
     */
    void submit(Task_interface * task);

    /**
     * @brief Передача заданной функции для параллельного исполнения.
     * 
     * @param function Функция для параллельного выполнения.
     * 
     * @details Данный метод можно использовать для передачи на параллельное исполнение в пуле потоков лямбды.
    */
    void submit(std::function<void()> function);

    /**
     * @brief Начать выполнение задач из очереди.
     * 
     */
    void start();

    /**
     * @brief Остановить выполнение
     * 
     */
    void stop();

    /**
     * @brief Возвращает размер пула потоков.
     * 
     * @return size_t - размер пула потоков.
     * 
     * @details
     * Возвращает количество потоков в пуле (т.е. размер пула потоков).
     * 
     */
    size_t size() const { return _number_of_threads; } 

    /**
     * @brief Возвращает примерный размер очереди.
     * 
     * @return size_t Значение примерного размера очереди.
     * 
     * @attention Необходимо учитывать, что использование данного метода не гарантирует 
     * определённое им состояние очереди на момент использования полученного данным методом результата.
     * 
     */
    size_t queue_length() const { return _task_queue.size(); }
};

}

#endif
